package thread3;

import java.util.concurrent.*;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 查看当前电脑是几核的
        // 核，你可以想象成人脑子，一个核就是一个脑子，四核就说明CPU有四个脑子，脑子越多解决问题的速度越快，
        // Intel的核技术，可以把一个核模拟分成两个线程来用，充分的发挥了cpu的性能，
        // 线程8就代表核心数是4核的处理器可以模拟出8个线程来使用。线程我们可以理解每个大脑同时能处理多少件事。
        System.out.println(Runtime.getRuntime().availableProcessors());

        // 线程池的作用：控制运行的线程数量
        // 线程池的特点：线程复用，控制最大并发数量，管理线程
        // 线程池的处理过程：先将任务放入队列，在线程创建后启动这些任务，如果线程数量超过了最大数量，那么超出的数量线程排队等候，
        // 等待其他线程执行完成后，再从队列中取出任务来执行

        // 线程池的好处：
        // 1、降低资源消耗。通过重复利用已经创建的线程降低线程创建和消耗造成的消耗
        // 2、提高响应速度。当任务到达时，不需要等待线程创建就能立即执行
        // 3、提高线程的可管理性，线程是稀缺资源，如果无限的创建，不仅会消耗系统资源，且还会降低系统的稳定性，
        // 使用线程池可以进行同一的分配，调优和监控
        // 底层都是ThreadPoolExecutor

        // 线程池固定线程数量
        ExecutorService service = Executors.newFixedThreadPool(3);

        try {
            // execute和submit都属于线程池的方法，execute只能提交Runnable类型的任务，而submit既能提交Runnable类型任务也能提交Callable类型任务。
            // execute会直接抛出任务执行时的异常，submit会吃掉异常，可通过Future的get方法将任务执行时的异常重新抛出。
            // execute所属顶层接口是Executor，submit所属顶层接口是ExecutorService
            // 实现类ThreadPoolExecutor重写了execute方法，抽象类AbstractExecutorService重写了submit方法。
            for (int i = 0; i < 6; i++) {
                service.execute(() -> System.out.println(Thread.currentThread().getName()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }

        // 线程池内只有1个线程
        ExecutorService service2 = Executors.newSingleThreadExecutor();
        // 创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。
        ExecutorService service3 = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 2; i++) {
                service3.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }

        /*
        提交任务后，线程池会做如下判断：
        1、如果正在运行的线程数量小于coreSize，那么会马上创建线程运行这个任务
        2、如果正在运行的线程数量大于或等于coreSize，那么会将这个任务放入阻塞队列中
        3、如果这个时候队列满了，且在运行的线程数量还小于maximumPoolSize，那么会创建非核心线程来立刻执行这个任务
        4、如果队列满了，且在运行的线程数量大于等于maximumPoolSize，那么线程池会启动饱和拒绝策略来执行
        */

        /*
        jdk内置的拒绝策略（4种）：
        第一种：AbortPolicy（默认），直接抛异常
        第二种：CallerRunsPolicy，不抛弃任务，不抛出异常，而是将某些任务回退到调用方
        第三种：DiscardOldestPolicy，抛弃队列中等待最久的任务，然后把当前任务加入到队列中再次尝试提交当前任务
        第四种：DiscardPolicy，直接丢弃任务，不处理也不抛异常。如果允许任务丢失，那么这是一种最好的方案
        */

        /*
            int corePoolSize, 常驻核心数
            int maximumPoolSize, 最大核心数
            long keepAliveTime, 多余的空闲线程的存活时间，当线程中的线程数量超过corePoolSize时，当空闲时间到达keepAliveTime时，多余
            空闲线程会被销毁直到只剩corePoolSize个线程为止
            TimeUnit unit, keepAliveTime的单位
            BlockingQueue<Runnable> workQueue, 阻塞队列，用于存储多余的任务
            ThreadFactory threadFactory, 创建线程的线程工厂，一般默认就行
            RejectedExecutionHandler handler, 拒绝策略
        */

        /*
        生产中我们会自定义线程池，而不用自定义的线程池，原因是会导致OOM（默认的线程池的阻塞队列大小都是Integer.MAX_VALUE，会导致OOM）
        public LinkedBlockingDeque() {
            this(Integer.MAX_VALUE);
        }
         */
        ExecutorService executorService = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        /*
        如何配置线程池的数量？
        1、首先要调用Runtime.getRuntime().availableProcessors()知道当前服务器的核数

        cpu密集型：
        意思是该任务需要大量的运算，而没有阻塞，cpu一直全速运行，cpu密集任务只有在真正的多核cpu上才可能得到加速（通过多线程），
        而单核cpu上，无论你开几个模拟器的多线程任务都不可能得到加速，因为cpu的总运算能力就那些

        cpu密集型任务配置尽可能少的线程数量：
        一般公式：cpu核数+1个线程的线程池

        IO密集型：
        IO密集型，即该任务需要大量的IO，即大量的阻塞，在单线程上运行IO密集型的任务会浪费大量的cpu运算能力浪费在等待。
        所以在IO密集型任务中使用多线程可以大大的加速程序运行，即使在单核cpu上，这种加速主要就是利用了被浪费掉的阻塞时间

        由于IO密集型任务并不是一直在执行任务，则应配置尽可能多的线程，如：cpu核数*2
        IO密集型时，大部分线程都阻塞，故需要多配置线程数：
        参考公式：CPU核数 / 1 - 阻塞系数 （阻塞系数在0.8~0.9之间）
        比如8核cpu：8 / 1 -0.9 = 80个线程
        */
    }
}
